#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <cstring>
#include "log_macro.hpp"

using namespace std;

const string default_ip = "0.0.0.0";
const uint16_t default_port = 8080;

class Socket {
    static const int default_backlog = 5;
public:
    Socket() :_sockfd(-1) {}

    Socket(int fd) : _sockfd(fd) {}

    ~Socket() {
        close();
    }

    void socket() {
        _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
        if(_sockfd < 0) {
            FATAL_LOG("socket error: %s", strerror(errno));
            exit(errno);
        }
        set_sock_reuseaddr();
        INFO_LOG("socket success, _sockfd = %d", _sockfd);
        
    }

    void bind(uint16_t port = default_port) {
        struct sockaddr_in localhost;
        localhost.sin_family = AF_INET;
        localhost.sin_port = htons(port);
        localhost.sin_addr.s_addr = INADDR_ANY;
        if(::bind(_sockfd, (const struct sockaddr*)&localhost, sizeof(localhost)) < 0) {
            FATAL_LOG("bind error: %s", strerror(errno));
            exit(errno);
        }
        INFO_LOG("bind success");
    }

    void listen() {
        if(::listen(_sockfd, default_backlog) < 0) {
            FATAL_LOG("listen error: %s", strerror(errno));
            exit(errno);
        }
        INFO_LOG("listen success");
    }

    int accept(bool print_client_info = true) {
        struct sockaddr_in client_addr; 
        socklen_t client_len = sizeof(client_addr);
        int newfd = ::accept(_sockfd, (struct sockaddr *)&client_addr, &client_len);
        if(newfd < 0) {
            WARNING_LOG("accept error: %s", strerror(errno));
            return -1;
        }
        if(print_client_info) {
            char ip_buff[INET_ADDRSTRLEN];
            if(inet_ntop(client_addr.sin_family, &client_addr.sin_addr, ip_buff, sizeof(ip_buff)) != nullptr) {
                INFO_LOG("get a new link, client_ip = %s, clinet_port = %d", ip_buff, ntohs(client_addr.sin_port));
            }
        }
        INFO_LOG("accept success, newfd = %d", newfd);
        return newfd;
    }

    void connect(const string &ip, uint16_t port) {
        struct sockaddr_in serverhost;
        serverhost.sin_family = AF_INET;
        serverhost.sin_port = htons(port);
        
        if(inet_pton(AF_INET, ip.c_str(), &(serverhost.sin_addr)) != 1) {
            FATAL_LOG("inet_pton() failed");
            exit(errno);
        }
        if(::connect(_sockfd, (const struct sockaddr *)&serverhost, sizeof(serverhost)) < 0) {
            FATAL_LOG("connect error: %s", strerror(errno));
            exit(errno);
        }
        INFO_LOG("connect success");
    }

    void close() {
        if(_sockfd != -1) {
            ::close(_sockfd);
        }
    }

    int get_sockfd() const {
        return _sockfd;
    }

    ssize_t recv(void *buf, size_t len, bool blocked = true) const {
        if(!blocked) {
            set_nonblock(_sockfd);
        }
        ssize_t n = ::recv(_sockfd, buf, len, 0);
        if(n <= 0) {
            if(errno == EINTR || errno == EWOULDBLOCK) {
                return 0;
            }
            else if(n == 0) {
                WARNING_LOG("client closed");
            }
            else {
                ERROR_LOG("recv error: %s", strerror(errno));
            }
            exit(errno);
        }
        return n;
    }

    ssize_t send(const void *buf, size_t len, bool blocked = true) const {
        if(!blocked) {
            set_nonblock(_sockfd);
        }
        ssize_t n = ::send(_sockfd, buf, len, 0);
        if(n <= 0) {
            if(errno == EINTR || errno == EWOULDBLOCK) {
                return 0;
            }
            else if(n == 0) {
                WARNING_LOG("client closed");
            }
            else {
                ERROR_LOG("send error: %s", strerror(errno));
            }
            exit(errno);
        }
        return n;
    }

    void set_nonblock(int fd) const {
        int flag = fcntl(fd, F_GETFL);
        if(flag == -1) {
            ERROR_LOG("Failed to set flag");
            return;
        }
        if (fcntl(fd, F_SETFL, flag | O_NONBLOCK) == -1) {  
            ERROR_LOG("fcntl F_SETFL");  
            return;  
        }
    }

    void create_server() {
        socket();
        set_sock_reuseaddr();
        bind();
        listen();
    }

    void create_client(const string &ip, const uint16_t port) {
        socket();
        connect(ip, port);
    }

    void set_sock_reuseaddr() const {
        int yes = 1;
        if (setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) {  
            FATAL_LOG("setsockopt error: %s", strerror(errno));
            exit(errno);  
        }
    }

    int get_fd() const {
        return _sockfd;
    }

private:
    int _sockfd;
};